package com.ruoyi.outpatient.core;

import com.google.common.hash.Funnels;
import com.google.common.hash.Hashing;
import com.google.common.primitives.Longs;
import com.ruoyi.common.core.redis.RedisCache;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.nio.charset.Charset;

/**
 * @author 伟峰
 * @date 2022/5/12
 * @description: 自定义基于redis的布隆过滤器
 */



@Component
public class RedisBloomFilter {

    public final static String RS_BF_NS_="rbf:"; //key的前缀

    private int numApproxElements; // 预估元素数量
    private double fpp; //可接受最大误差
    private int bitmapLength; // 自动计算的最优Bigmap长度  位数组长度
    private int numHashFunctions; //计算hash函数个数


    @Autowired
    private RedisCache redisCache;


    /**
     *  先对布隆过滤器进行初始化
     * @param numApproxElements 预估元素数量
     * @param fpp 可接受最大误差
     * @return
     */
    public RedisBloomFilter init(int numApproxElements,double fpp) {
        this.numApproxElements=numApproxElements;
        this.fpp=fpp;
//        计算位数组长度
        this.bitmapLength=(int)(-numApproxElements*Math.log(fpp)/(Math.pow(Math.log(2),2)));
//        计算hash函数个数
        this.numHashFunctions=Math.max(1,(int)Math.round((double)bitmapLength/numApproxElements*Math.log(2)));
        return this;
    }


//    10万,容错率0.01的初始化
    public RedisBloomFilter init() {
        this.numApproxElements=numApproxElements;
        this.fpp=0.01;
//        计算位数组长度
        this.bitmapLength=(int)(-100000*Math.log(0.01)/(Math.pow(Math.log(2),2)));
//        计算hash函数个数
        this.numHashFunctions=Math.max(1,(int)Math.round((double)bitmapLength/100000*Math.log(2)));
        return this;
    }




    /**
     * 计算用户存入过滤器时,该元素会被hash到对应的下标
     * 这里我们使用两个hash来模拟多个hash
     * @param element 元素
     * @return 位图数组
     */
    private  long[] getBitIndics(Long element){
        long[] indics=new long[numHashFunctions]; //生成一个位图数组

//        1.使用谷歌提供hash函数来将我们的元素转为128位的hash值: (Hashing.murmur3_128().hashObject(element))
//        2.并且该元素要设为utf-8:    Funnels.stringFunnel(Charset.forName("UTF-8")
//        3. 再将128位的hash值转为byte数组(16个字节):   .asBytes()

//        byte[] bytes = Hashing.murmur3_128().hashObject(element, Funnels.stringFunnel(Charset.forName("UTF-8"))).asBytes();
        byte[] bytes = Hashing.murmur3_128().hashLong(element).asBytes();


//        将16个字节分为两个8字节的hash值
       long hash1= Longs.fromBytes(bytes[7],bytes[6],bytes[5],bytes[4],bytes[3],bytes[2],bytes[1],bytes[0]);
       long hash2=Longs.fromBytes(bytes[15],bytes[14],bytes[13],bytes[12],bytes[11],bytes[10],bytes[9],bytes[8]);


       long temporaryHash=hash1; //设个临时表里来存储hash1,防止hash1被修改 后面可能会用到hash1
       for (int i=0;i<numHashFunctions;i++){
//          1. 将temporaryHash设置成一个正数:  temporaryHash & Long.MAX_VALUE
//          2. 模上位图长度就能知道在那个位置了:  % bitmapLength
           indics[i]=(temporaryHash & Long.MAX_VALUE) % bitmapLength;
           temporaryHash=temporaryHash+hash2;
       }

        System.out.print(element+"数组");
       for (long index:indics){
           System.out.println(index+",");
       }
        System.out.println(" ");

       return indics;

    }


    /**
     *  将布隆过滤器hash到的数组值插入bigmap
     * @param key 过滤器的键
     * @param element 元素 使用getBitIndics转为hash数组
     * @param expire 过期时间
     */
    public void insert(final String key, final Long element, long expire){
        long[] bitIndics = getBitIndics(element);

        // TODO: 2022/5/13 后面使用管道插入
        for (long l:bitIndics){
            redisCache.setBig(key,l,true);
        }
//        设置过期时间
        redisCache.expire(key,expire);
    }

//    不设置过期时间
    public void insert(final String key,final Long element){
        long[] bitIndics = getBitIndics(element);

        // TODO: 2022/5/13 后面使用管道插入
        for (long l:bitIndics){
            redisCache.setBig(key,l,true);
        }
    }




    /**
     * 该元素在bigmap中是否存在
     * @param key 键
     * @param element 判断的元素
     *
     */
    public Boolean isBool(final String key,final Long element){

//        将元素转为hash数组
        long[] bitIndics = getBitIndics(element);

//        遍历hash数组,只要有一个为false说明在布隆过滤器中不存在,如果全为true那么就存在
        for (long l:bitIndics){

            Boolean big = redisCache.getBig(key, l);
//            只要有一个不存在则返回
            if (!big){
                return false;
            }
        }
        return true;

    }


}
